/*
 Copyright (c) 2009, Ivan Popivanov
 All rights reserved.

 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions
 are met:

 * Redistributions of source code must retain the above copyright
 notice, this list of conditions and the following disclaimer.

 * Redistributions in binary form must reproduce the above copyright
 notice, this list of conditions and the following disclaimer in the
 documentation and/or other materials provided with the distribution.

 * Neither the name of StockCandles nor the names of its contributors
 may be used to endorse or promote products derived from this software
 without specific prior written permission.

 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#ifdef _MSC_VER
#pragma warning(disable:4715)
#endif // _MSC_VER

#ifndef PLOT_H
#define PLOT_H

#include <math.h>

#include <QtGui/QWidget>
#include <QtGui/QFont>
#include <QtGui/QFontMetrics>
#include <QtGui/QColor>
#include <QtCore/QPair>

#include "Indicator.h"
#include "Line.h"

class Plot : public QWidget
{
   Q_OBJECT

public:
   enum PlotScale
   {
      Linear,
      Log
   };

public:
   Plot(QWidget *parent = 0);
   ~Plot();

   void setOhlc(const Ohlc &ohlc, int ohlcId = 0);
   void rightMostOhlcChanged(int ohlcId);

   void initIndicators();
   void initLines(const Ohlc &ohlc);

protected:
   virtual void paintEvent(QPaintEvent *pe);
   virtual void resizeEvent(QResizeEvent *re);

   void drawVAxis(QPainter &painter);
   void drawHLines(QPainter &painter);
   void drawHAxis(QPainter &painter);
   void drawVLines(QPainter &painter);

   void drawVAxisLabels(QPainter &painter);
   void drawHAxisLabels(QPainter &painter);

   int calcVAxisWidth() const;
   int calcHAxisHeight() const;

   void drawCandles(QPainter &painter, const Line &line, uint first);
   void drawSolidLine(QPainter &painter, const Line &line, uint first);

private:
   static const int DOLLAR_FRACTIONS = 3;
   static const double dollarFractions[DOLLAR_FRACTIONS];

   struct Label
   {
      // The anchor point defining the location - use to draw the grid line
      qreal x;

      // The label text
      QString text;

      // The bounding rectangle of the text
      QRectF brc;

      // The point to pass to QPainter::drawText to draw at the desired location
      QPointF xy;

      // 'x' is the middle and 'y' is the top
      void init(QFontMetrics &fm, QString text, qreal x, qreal y)
      {
         this->text = text;
         this->x = x;
         this->brc = fm.boundingRect(this->text);

         // The coordinates for drawing the text - left is x+1
         this->xy.setX(this->x + 2 - this->brc.left());
         this->xy.setY(y - this->brc.top());

         // Translate the bounding rectangle to the real text location
         this->brc.moveLeft(this->x + 2);
         this->brc.moveTop(y);
      }
   };

private:
   double scale(double d);

   void calcDrawingArea(const Line &mainLine);
   void calcVAxisLabels();
   void calcHAxisLabels(const Ohlc &ohlc);

   void calcMinMax(const Ohlc &ohlc, const Line &mainLine);

   void addLabels(QVector<Label> &added, QVector<Label> &input, bool allOrNone);

private:
   // The original ohlc
   Ohlc originalOhlc;

   // Buffer for drawing
   QPixmap *buffer;

   // The index of the first ohlc on the right of the chart
   uint rightMostOhlc;

   // The scale of the plot
   PlotScale plotScale;

   // Number of visible bars
   uint bars;

   // The width of a candle, including the area not covered by the body
   uint barWidth;

   // The candle body width <= barWidth
   uint barBodyWidth;

   // The number of Ohlcs each bar represents - when showing a large amount of data,
   // first we decrease the size of the candle, but then we start merging ohlcs into
   // bars. Thus, one bar may represent 10 ohlcs - the open, close, high, low are:
   //    - open is the open of the first ohlc
   //    - close is the close of the last ohlc
   //    - high is the highest value among the highs
   //    - low is the lowest value among the lows
   uint ohlcsPerBar;

   // The merged series of ohlcs
   Ohlc mergedOhlc;

   QList<Indicator> indicators;
   QList<Line> plotLines;

   // MIN/MAX for the candles visible - we stretch the series
   // from top to bottom of the area we use for painting
   double visibleMin;
   double visibleMax;

   double scaledMin;
   double scaledMax;

   double shearFactor;

   // The thickness of the axis line
   int vAxisWidth;

   int majorTickWidth;
   int minorTickWidth;

   // The width of the area reserved for the vertical axis
   int vAxisAreaWidth;
   QFont vAxisFont;
   QColor vAxisTextColor;
   
   // The thickness of the axis line
   int hAxisWidth;

   int majorTickHeight;
   int minorTickHeight;

   // The height of the area reserved for the horizontal axis
   int hAxisAreaHeight;
   QFont hAxisFont;
   QColor hAxisTextColor;

   QColor axisColor;

   // The vertical axis labels
   QVector<double> vAxisLabels;

   // Number of decimal places for the vertical labels
   int vLabelPrecision;

   int minVLabels;

   QRectF ohlcArea;
   QRectF vAxisArea;
   QRectF hAxisArea;

   // Three levels of horizontal labels
   QVector<Label> majorHLabels;
   QVector<Label> minorHLabels;
   QVector<Label> microHLabels;
};

inline double
Plot::scale(double d)
{
   switch(plotScale)
   {
   case Linear: return d;
   case Log: return log(d)/log(2.0);
   }

   Q_ASSERT(false);
}

inline int
Plot::calcVAxisWidth() const
{
   // Compute the number of decimal digits of the MAX
   int digits = (int)floor(log10(visibleMax)) + 1;
   QString str(digits + 3, '8');

   QFontMetrics metrics(vAxisFont);
   QRect rect = metrics.boundingRect(str);

   int width = rect.width();

   // Add the axis width
   width += vAxisWidth;

   // Add the tick width
   width += majorTickWidth;

   // Add one pixel spacing between the text and the tick
   width += 1;

   return width;
}

inline int
Plot::calcHAxisHeight() const
{
   QString str(3, 'W');

   QFontMetrics metrics(hAxisFont);
   QRect rect = metrics.boundingRect(str);

   int height = rect.height();

   // Add the axis width
   height += hAxisWidth;

   // Add the tick width
   height += majorTickHeight;

   // Add one pixel spacing between the text and the tick
   height += 1;

   return height;
}

#endif // PLOT_H